package czh.cocoa.graphics;

import java.util.Map;
import java.util.Stack;

import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Xfermode;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.PorterDuff;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.Shader.TileMode;
import android.graphics.Typeface;
import czh.cocoa.foundation.NSObject;
import czh.cocoa.foundation.NSString;

import static czh.cocoa.graphics.CGGradient.*;

public class CGContext extends NSObject {

	private Canvas mCanvas;
	
	private Paint mPaint;
	
	private CGMutablePath mPath;
	
	private Stack<State> mStateStack;
	
	private State mState;
	
	private CGPoint mTextPosition;
	
	public CGContext(Canvas canvas) {
		mCanvas = canvas;
		mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
		mState = new State();
		mStateStack = new Stack<State>();
		mTextPosition = new CGPoint();
	}
	
	public void beginPath() {
		mPath = new CGMutablePath();
	}
	
	public void moveToPoint(float x, float y) {
		mPath.moveToPoint(null, x, y);
	}
	
	public void addLineToPoint(float x, float y) {
		mPath.addLineToPoint(null, x, y);
	}
	
	public void addPath(CGPath path) {
		mPath.addPath(null, path);
	}
	
	public void addArc(float x, float y, float radius, float startAngle, float endAngle, int clockwise) {
		mPath.addArc(null, x, y, radius, startAngle, endAngle, clockwise); 
	}
	
	public void closePath() {
		mPath.closeSubpath();
	}
	
	public void strokePath() {
		mPaint.setStyle(Style.STROKE);
		//mPaint.setStrokeWidth(mState.lineWidth);
		
		CGColor color = mState.strokeColor;
		if (color.pattern() != null) { 
			Bitmap bitmap = color.pattern().bitmap();
			BitmapShader shader = new BitmapShader(bitmap, TileMode.REPEAT, TileMode.REPEAT); 
			mPaint.setShader(shader);
		} else {
			mPaint.setColor(color.intValue());
		}
		
		mCanvas.drawPath(mPath.getPath(), mPaint);
	}
	
	public void fillPath() {
		mPaint.setStyle(Style.FILL);
		
		CGColor color = mState.fillColor;
		if (color.pattern() != null) { 
			Bitmap bitmap = color.pattern().bitmap();
			BitmapShader shader = new BitmapShader(bitmap, TileMode.REPEAT, TileMode.REPEAT); 
			mPaint.setShader(shader);
		} else {
			mPaint.setColor(color.intValue());
		}
		
		mCanvas.drawPath(mPath.getPath(), mPaint);
	}
	
	public void setStrokeColor(float[] components) {
		mState.strokeColor = new CGColor(components);
	}
	
	public void setFillColor(float[] components) {
		mState.fillColor = new CGColor(components);
	}
	
	public void setStrokeColorWithColor(CGColor color) {
		mState.strokeColor = new CGColor(color);
		mPaint.setColor(color.intValue()); 
	}
	
	public void setFillColorWithColor(CGColor color) {
		mState.fillColor = new CGColor(color);
		mPaint.setColor(color.intValue()); 
	}
	
	public void setLineWidth(float width) {
		//mState.lineWidth = width;
		mPaint.setStrokeWidth(width);
	}
	
	public void saveGSState() {
		mCanvas.save();
		
		mState.lineWidth = mPaint.getStrokeWidth();
		mState.xfermode = mPaint.getXfermode();
		
		mStateStack.push(mState);
		mState = new State(mState);
	}
	
	public void restoreGSState() {
		mCanvas.restore();
		mState = mStateStack.pop();
		
		mPaint.setStrokeWidth(mState.lineWidth);
		mPaint.setXfermode(mState.xfermode);
	}
	
	public void fillRect(CGRect rect) {
		beginPath();
		moveToPoint(rect.origin.x, rect.origin.y);
		addLineToPoint(rect.origin.x + rect.size.width, rect.origin.y);
		addLineToPoint(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height);
		addLineToPoint(rect.origin.x, rect.origin.y + rect.size.height);
		closePath();
		
		fillPath();
	}
	
	public void strokeRect(CGRect rect) {
		beginPath();
		moveToPoint(rect.origin.x, rect.origin.y);
		addLineToPoint(rect.origin.x + rect.size.width, rect.origin.y);
		addLineToPoint(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height);
		addLineToPoint(rect.origin.x, rect.origin.y + rect.size.height);
		closePath();
		
		strokePath();
	}
	
	public void drawImage(CGRect rect, CGImage image) {
		Bitmap bitmap = image.bitmap();
		if (bitmap != null) {
			mPaint.setAntiAlias(false);
			mCanvas.drawBitmap(bitmap, null, new RectF(rect.origin.x, rect.origin.y, 
					rect.origin.x + rect.size.width, rect.origin.y + rect.size.height), mPaint);
			mPaint.setAntiAlias(true);
		}
	}
	
	public void selectFont(NSString fontName, float fontSize) {
		CGFont font = new CGFont(fontName);
		setFont(font);
		setFontSize(fontSize);
	}
	
	public void setFont(CGFont font) {
		mPaint.setTypeface(font.typeface());
	}
	
	public void setFontSize(float fontSize) {
		mPaint.setTextSize(fontSize);
	}
	
	public void showText(String text) {
		showTextAtPoint(mTextPosition.x, mTextPosition.y, text);
	}
	
	public void showTextAtPoint(float x, float y, String text) {
		mPaint.setStyle(Style.FILL_AND_STROKE);
		mPaint.setColor(mState.fillColor.intValue());
		mCanvas.drawText(text, x, y - mPaint.ascent(), mPaint);
		
		mTextPosition.x = x + mPaint.measureText(text);
		mTextPosition.y = y;
	}
	
	public CGPoint getTextPosition() {
		return new CGPoint(mTextPosition);
	}
	
	//addition
	public float getTextAscent() {
		return -mPaint.ascent();
	}
	
	//addition
	public float getTextDescent() {
		return -mPaint.descent();
	}
	
	//addition
	public Paint getPaint() {  
		return mPaint;
	}
	
	public void translateCTM(float tx, float ty) {
		mCanvas.translate(tx, ty);
	}
	
	public void drawLinearGradient(CGGradient gradient, CGPoint startPoint, CGPoint endPoint, int options) {
		CGColor[] cg_colors = gradient.colors();
		int[] colors = new int[cg_colors.length];
		for (int i = 0; i < colors.length; i++) {
			colors[i] = cg_colors[i].intValue();
		}
		
		float[] positions = gradient.locations();
		
		Shader.TileMode tile_mode;
		switch (options) {
		case kCGGradientDrawsClamp:
			tile_mode = Shader.TileMode.CLAMP;
			break;
		case kCGGradientDrawsMirror:
			tile_mode = Shader.TileMode.MIRROR;
			break;
		case kCGGradientDrawsRepeat:
		default:
			tile_mode = Shader.TileMode.REPEAT;
			break;
		}
		
		LinearGradient shader = new LinearGradient(startPoint.x, startPoint.y, endPoint.x, endPoint.y,	
				colors, positions, tile_mode);
		Paint paint = new Paint();
		paint.setShader(shader);
		mCanvas.drawPaint(paint); 

	}
	
	public void clip() {
		if (mPath != null) {
			mCanvas.clipPath(mPath.getPath()); 
		}
	}
	
	public void setBlendMode(CGBlendMode blendMode) {
		if (blendMode == null) {
			mPaint.setXfermode(null);
			return;
		}
		
		PorterDuff.Mode mode;
		switch (blendMode) {
		case Normal:
			 mode = PorterDuff.Mode.SRC; 
			break;
		case Multiply:
			mode = PorterDuff.Mode.MULTIPLY;
			break;
		case Clear:
			mode = PorterDuff.Mode.CLEAR;
			break;
		case SourceIn:
			mode = PorterDuff.Mode.SRC_IN;
			break;
		case SourceOut:
			mode = PorterDuff.Mode.SRC_OUT;
			break;
		case SourceAtop:
			mode = PorterDuff.Mode.SRC_ATOP;
			break;
		case DestinationOver:
			mode = PorterDuff.Mode.DST_OVER;
			break;
		case DestinationIn:
			mode = PorterDuff.Mode.DST_IN;
			break;
		case DestinationOut:
			mode = PorterDuff.Mode.DST_OUT;
			break;
		case DestinationAtop:
			mode = PorterDuff.Mode.DST_ATOP;
			break;
		case XOR:
			mode = PorterDuff.Mode.XOR;
			break;
		default:
			mode = PorterDuff.Mode.SRC;  
			break;
		}
		
//        new PorterDuffXfermode(PorterDuff.Mode.CLEAR),
//        new PorterDuffXfermode(PorterDuff.Mode.SRC),
//        new PorterDuffXfermode(PorterDuff.Mode.DST),
//        new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER),
//        new PorterDuffXfermode(PorterDuff.Mode.DST_OVER),
//        new PorterDuffXfermode(PorterDuff.Mode.SRC_IN),
//        new PorterDuffXfermode(PorterDuff.Mode.DST_IN),
//        new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT),
//        new PorterDuffXfermode(PorterDuff.Mode.DST_OUT),
//        new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP),
//        new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP),
//        new PorterDuffXfermode(PorterDuff.Mode.XOR),
//        new PorterDuffXfermode(PorterDuff.Mode.DARKEN),
//        new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN),
//        new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY),
//        new PorterDuffXfermode(PorterDuff.Mode.SCREEN)
		
		PorterDuffXfermode xfermode = new PorterDuffXfermode(mode);
		mPaint.setXfermode(xfermode);

	}
	
	public Canvas canvas() {
		return mCanvas;
	}
	
	private class State {
		
		public CGColor strokeColor;
		
		public CGColor fillColor;
		
		public float lineWidth;
		
		public Xfermode xfermode;
		
		public State() {
			strokeColor = new CGColor();
			fillColor = new CGColor();
		}
		
		public State(State state) {
			strokeColor = state.strokeColor;
			fillColor = state.fillColor;
			lineWidth = state.lineWidth;
			xfermode = state.xfermode;
		}
	
		
	}
	
//static calls
	
	public static void CGContextBeginPath(CGContext ctx) {
		ctx.beginPath();
	}
	
	public static void CGContextMoveToPoint(CGContext ctx, float x, float y) {
		ctx.moveToPoint(x, y);
	}
	
	public static void CGContextAddLineToPoint(CGContext ctx, float x, float y) {
		ctx.addLineToPoint(x, y); 
	}
	
	public static void CGContextAddArc(CGContext ctx, float x, float y, float radius, float startAngle, 
			float endAngle, int clockwise) {
		ctx.addArc(x, y, radius, startAngle, endAngle, clockwise);
	}
	
	public static void CGContextAddPath(CGContext ctx, CGPath path) {
		ctx.addPath(path);
	}
	
	public static void CGContextClosePath(CGContext ctx) {
		ctx.closePath();
	}
	
	public static void CGContextStrokePath(CGContext ctx) {
		ctx.strokePath();
	}
	
	public static void CGContextFillPath(CGContext ctx) {
		ctx.fillPath();
	}
	
	public static void CGContextSetStrokeColor(CGContext ctx, float[] components) {
		ctx.setStrokeColor(components);
	}
	
	public static void CGContextSetFillColor(CGContext ctx, float[] components) {
		ctx.setFillColor(components);
	}
	
	public static void CGContextSetStrokeColorWithColor(CGContext ctx, CGColor color) {
		ctx.setStrokeColorWithColor(color);
	}
	
	public static void CGContextSetFillColorWithColor(CGContext ctx, CGColor color) {
		ctx.setFillColorWithColor(color);
	}
	
	public static void CGContextSaveGSState(CGContext ctx) {
		ctx.saveGSState();
	}
	
	public static void CGContextRestoreGSState(CGContext ctx) {
		ctx.restoreGSState();
	}
	
	public static void CGContextSetLineWidth(CGContext ctx, float width) {
		ctx.setLineWidth(width);
	}
	
	public static void CGContextFillRect(CGContext ctx, CGRect rect) {
		ctx.fillRect(rect); 
	}
	
	public static void CGContextStrokeRect(CGContext ctx, CGRect rect) {
		ctx.strokeRect(rect); 
	}
	
	public static void CGContextDrawImage(CGContext ctx, CGRect rect, CGImage image) {
		ctx.drawImage(rect, image);
	}
	
	public static void CGContextSelectFont(CGContext ctx, NSString fontName, float fontSize) {
		ctx.selectFont(fontName, fontSize);
	}
	
	public static void CGContextSetFont(CGContext ctx, CGFont font) {
		ctx.setFont(font);
	}
	
	public static void CGContextSetFontSize(CGContext ctx, float fontSize) {
		ctx.setFontSize(fontSize);
	}
	
	public static void CGContextShowText(CGContext ctx, String text) {
		ctx.showText(text);
	}
	
	public static void CGContextShowTextAtPoint(CGContext ctx, float x, float y, String text) {
		ctx.showTextAtPoint(x, y, text);
	}
	
	public static CGPoint CGContextGetTextPosition(CGContext ctx) {
		return ctx.getTextPosition();
	}
	
	//addition
	public static float CGContextGetTextAscent(CGContext ctx) {
		return ctx.getTextAscent();
	}
	
	//addition
	public static float CGContextGetTextDescent(CGContext ctx) {
		return ctx.getTextDescent();
	}
	
	//addition
	public static Paint CGContextGetPaint(CGContext ctx) {
		return ctx.getPaint();
	}
	
	public static void CGContextTranslateCTM(CGContext ctx, float tx, float ty) {
		ctx.translateCTM(tx, ty); 
	}
	
	public static void CGContextDrawLinearGradient(CGContext ctx, CGGradient gradient, 
			CGPoint startPoint, CGPoint endPoint, int options) {
		ctx.drawLinearGradient(gradient, startPoint, endPoint, options); 
	}
	
	public static void CGContextClip(CGContext ctx) {
		ctx.clip();
	}
	
	public static void CGContextSetBlendMode(CGContext ctx, CGBlendMode blendMode) {
		ctx.setBlendMode(blendMode);
	}
	
}




